package org.xx.armory.commons;

import org.apache.commons.lang3.time.DateFormatUtils;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.CharUtils.isAsciiPrintable;
import static org.apache.commons.lang3.CharUtils.unicodeEscaped;
import static org.apache.commons.lang3.StringUtils.join;
import static org.xx.armory.commons.Converter.toStr;


/**
 * 调试用的工具类。
 */
public final class DebugUtils {
    private static final int MAX_TRACE_BACK_DEPTH = 12;

    private DebugUtils() {
        throw new AssertionError();
    }

    /**
     * 在标准输出打印多个值。
     * <p>如果参数 {@code objects} 是 {@code null} 或者长度为 {@code 0}，那么打印空行。</p>
     *
     * @param objects
     *         待打印的多个值。
     */
    public static void println(
            Object... objects
    ) {
        final var l = objects != null ? objects.length : 0;
        for (var i = 0; i < l; ++i) {
            System.out.print(dumpObject(objects[i]));
            if (i < l - 1) {
                System.out.print(' ');
            }
        }
        System.out.println();
    }

    /**
     * 将指定的对象转为字符串输出。
     *
     * @param o
     *         待输出的对象。
     * @return 输出结果，如果参数{@code o}是{@code null}，那么输出结果是 {@literal null}。
     */
    public static String dumpObject(
            Object o
    ) {
        if (o == null) {
            return "null";
        } else if (o instanceof BigDecimal) {
            return "BigDecimal(\"" + ((BigDecimal) o).toPlainString() + "\")";
        } else if (o instanceof Number) {
            return o.toString();
        } else if (o instanceof CharSequence) {
            final var sb = new StringBuilder();
            sb.append('"');
            ((CharSequence) o).chars()
                              .forEach(ch -> {
                                  if (ch < 0x20 || ch > 0x7f) {
                                      sb.append("\\u").append(String.format("%04x", ch));
                                  } else if (ch == '\\') {
                                      sb.append('\\').append(ch);
                                  } else {
                                      sb.append((char) ch);
                                  }
                              });
            sb.append('"');
            return sb.toString();
        } else if (o instanceof Boolean) {
            return o.toString();
        } else if (o instanceof Date) {
            return DateFormatUtils.format((Date) o, "yyyy/MM/dd'T'HH:mm:ssZZ");
        } else if (o instanceof List) {
            return dumpList((List<?>) o);
        } else if (o instanceof Map) {
            return dumpMap((Map<?, ?>) o);
        } else if ("[B".equalsIgnoreCase(o.getClass().getName())) {
            return dumpBytes(o);
        } else if (o.getClass().isArray()) {
            return dumpArray(o);
        } else {
            return o.toString();
        }
    }

    /**
     * 将指定的对象转为字符串输出，输出结果中包含该对象的类型。
     *
     * @param o
     *         待输出的对象。
     * @return 输出结果，如果参数{@code o}是{@code null}，那么输出结果是 {@literal null}。
     */
    public static String dumpObjectAndType(
            Object o
    ) {
        if (o == null) {
            return dumpObject(null);
        } else {
            return "[" + o.getClass().getTypeName() + "]" + dumpObject(o);
        }
    }

    /**
     * 将集合转为字符串输出。
     *
     * @param l
     *         待输出的集合。
     * @return 输出结果。
     */
    public static String dumpList(
            Collection<?> l
    ) {
        if (l == null) {
            return dumpObject(null);
        }

        return "[" + join(l.stream().map(DebugUtils::dumpObject).toArray(String[]::new), ",") + "]";
    }

    /**
     * 将字节数组转为字符串输出。
     *
     * @param o
     *         待输出的字节数组。
     * @return 输出结果。
     */
    public static String dumpBytes(
            Object o
    ) {
        if (o == null) {
            return dumpObject(null);
        }

        final var result = new StringBuilder();

        result.append("b\"");

        final var l = Array.getLength(o);
        for (var i = 0; i < l; ++i) {
            final var bn = (Number) Array.get(o, i);
            if (bn != null) {
                final var b = bn.byteValue();
                if (b < 0x20) {
                    result.append(String.format("\\x%02x", b));
                } else if (b == (int) '"') {
                    result.append('\\').append('"');
                } else {
                    result.append((char) b);
                }
            }
        }

        result.append('"');

        return result.toString();
    }

    /**
     * 将数组转为字符串输出。
     *
     * @param o
     *         待输出的数组。
     * @return 输出结果。
     */
    public static String dumpArray(
            Object o
    ) {
        if (o == null) {
            return dumpObject(null);
        }

        final var result = new StringBuilder();

        result.append('[');

        final var l = Array.getLength(o);
        for (var i = 0; i < l; ++i) {
            result.append(Array.get(o, i));
            if (i != l - 1) {
                result.append(',');
            }
        }

        result.append(']');

        return result.toString();
    }

    /**
     * 将映射转为字符串输出。
     *
     * @param m
     *         待输出的映射。
     * @return 输出结果。
     */
    public static String dumpMap(
            Map<?, ?> m
    ) {
        if (m == null) {
            return dumpObject(null);
        }

        return "{"
                + join(m.entrySet().stream()
                        .map(entry -> entry.getKey() + ":" + dumpObject(entry.getValue()))
                        .toArray(String[]::new), ",")
                + "}";
    }

    /**
     * 比较两个字符串
     *
     * @param s1
     *         待比较的第一个字符串。
     * @param s2
     *         待比较的第二个字符串。
     * @return 比较结果。
     */
    public static StringComparisonResult compareString(
            String s1,
            String s2
    ) {
        if (s1 == null) {
            s1 = "";
        }
        if (s2 == null) {
            s2 = "";
        }
        final var iter1 = s1.codePoints().iterator();
        final var iter2 = s2.codePoints().iterator();

        var pos = 0;
        while (iter1.hasNext() && iter2.hasNext()) {
            final var i1 = iter1.nextInt();
            final var i2 = iter2.nextInt();

            if (i1 != i2) {
                return StringComparisonResult.of(pos, i1, i2);
            }

            ++pos;
        }

        if (iter1.hasNext()) {
            return StringComparisonResult.of(pos, iter1.next(), 0);
        } else if (iter2.hasNext()) {
            return StringComparisonResult.of(pos, 0, iter2.next());
        } else {
            return StringComparisonResult.NONE;
        }
    }

    /**
     * 输出字符串，包括长度和每个字符的UNICODE码。
     *
     * <p>
     * 输出格式为：[size=字符串长度,每个字符或者每个字符的UNICODE码(如果该字符不是可打印的Ascii字符)] 。 如果字符串的长度超过{@code 10}，那么只输出前{@code 10}个字符，然后加上{@code "..."}
     * 。
     * </p>
     *
     * <p>
     * 此方法会把不可打印字符也通过UNICODE码的形式输出，所以可以清楚地区分出真正的空字符串和只包含了不可打印字符的字符串。
     * </p>
     *
     * <pre>
     * {@code
     * dumpString(null) = "[null]";
     * dumpString("") = "[size=0,]";
     * dumpString("\u2bef\u7734") = "[size=2,\\u2bef\\u7734]";
     * dumpString("a quick fox jumps over") = "[size=22,a quick fo...]";
     * }
     * </pre>
     *
     * @param s
     *         原始字符串。
     * @return 包含了长度和字符信息的字符串。
     * @see org.apache.commons.lang3.CharUtils#isAsciiPrintable(char)
     * @see org.apache.commons.lang3.CharUtils#unicodeEscaped(char)
     */
    public static String dumpString(String s) {
        final var MAX_LEN = 10;

        if (s == null) {
            return "[null]";
        } else if (s.isEmpty()) {
            return "[size=0,]";
        } else {
            final StringBuilder sb = new StringBuilder();
            for (int i = 0; i < s.length() && i < MAX_LEN; ++i) {
                final char ch = s.charAt(i);
                sb.append(isAsciiPrintable(ch) ? ch : unicodeEscaped(ch));
            }
            return "[size=" + s.length() + "," + sb.toString() + (s.length() > MAX_LEN ? "...]" : "]");
        }
    }

    /**
     * 输出调用栈。
     *
     * @param ex
     *         待输出的异常。
     * @return 格式化后的调用栈信息。
     */
    public static List<String> dumpTraceBack(
            Throwable ex
    ) {
        final var result = new ArrayList<String>();
        final var msg = ex.getLocalizedMessage();
        result.add(ex.getClass().getName() + ": " + (msg != null ? msg.strip() : ""));

        dumpTraceBack(asList(ex.getStackTrace()), result);

        return result;
    }

    /**
     * 输出调用栈。
     *
     * @return 格式化后的调用栈信息。
     */
    public static List<String> dumpTraceBack() {
        final var result = new ArrayList<String>();
        final var elements = stream(Thread.currentThread().getStackTrace()).skip(2).collect(toList());

        dumpTraceBack(elements, result);

        return result;
    }

    private static void dumpTraceBack(
            List<? extends StackTraceElement> src,
            List<? super String> dst
    ) {
        var pj = 0;
        var i = 0;
        var j = 0;
        for (final var ele : src) {
            if (j >= MAX_TRACE_BACK_DEPTH) {
                break;
            }
            ++i;

            final var emn = toStr(ele.getModuleName());
            final var ecn = ele.getClassName();
            if (emn.startsWith("java.")
                    || ecn.startsWith("java.") || ecn.startsWith("javax.") || ecn.startsWith("jdk.") || ecn.startsWith("oracle.")
                    || ecn.startsWith("org.hibernate.")
                    || ecn.startsWith("org.springframework.") || ecn.startsWith("com.sun.") || ecn.startsWith("org.apache.catalina.")
                    || ecn.startsWith("io.undertow") || ecn.startsWith("org.jboss")) {
                // 发现jdk类型，跳过。
                ++pj;
            } else {
                dumpOmittedJdkFrames(pj, dst);
                pj = 0;
                dst.add("  at " + ele.toString());
                ++j;
            }
        }
        dumpOmittedJdkFrames(pj, dst);
        if (i < src.size()) {
            dst.add("  ... " + (src.size() - i) + " frames omitted");
        }
    }

    private static void dumpOmittedJdkFrames(
            int count,
            List<? super String> dst
    ) {
        if (count <= 0) {
            return;
        }
        if (count == 1) {
            dst.add("  <1 internal call>");
        } else {
            dst.add("  <" + count + " internal calls>");
        }
    }
}
